/*
 * Copyright (c) 2010. Dylan Schell.
 */

package nl.xs4all.dylanschell.deckprinter.services;

import com.google.inject.Inject;
import com.itextpdf.text.BadElementException;
import com.itextpdf.text.Chunk;
import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.FontFactory;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfWriter;
import nl.xs4all.dylanschell.deckprinter.model.CardInDeck;
import nl.xs4all.dylanschell.deckprinter.model.CardInSet;
import nl.xs4all.dylanschell.deckprinter.model.DeckDescriptor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

public class PDFDeckPrinter implements DeckPrinter
{
    private static final Logger logger = Logger.getLogger("DeckPrinter");
    /**
     * Magic cards are 2 15/16" by 3 7/16" the images we're using have half
     * borders, so we need an area slightly smaller
     */
    public static final int PDF_RESOLUTION = 72;
    public static final double CARD_WIDTH = (2 * PDF_RESOLUTION) + (((15d / 16d) * PDF_RESOLUTION));
    public static final double CARD_HEIGHT = (3 * PDF_RESOLUTION) + (((7d / 16d) * PDF_RESOLUTION));
    public static final double ASPECT_RATIO = 480d / 680d; // 1.4166666
    public static final double IMAGE_WIDTH = 480;
    public static final double IMAGE_HEIGHT = 680;
    // these are determined by visual inspection of a print
    // but they should be accurate enough for now...
    public static final float WIDTH = 169.1f;
    public static final float HEIGHT = 239.5f;
    public static final int LEFT_MARGIN = 16;
    public static final int TOP_MARGIN = 16;
    public static final int CARD_MARGIN = 0;

    private final Map<CardInSet, Image> imageCache = new HashMap<CardInSet, Image>();
    private final ImageService imageService;
    private final SetService setService;
    private final DeckPrinterConfig config;

    @Inject
    public PDFDeckPrinter(ImageService imageService, SetService setService, DeckPrinterConfig config)
    {
        this.config = config;
        this.imageService = imageService;
        this.setService = setService;
    }

    public void printDeck(DeckDescriptor deck, File outputFile) throws IOException
    {
        logger.log(Level.INFO, "Output File: " + outputFile);
        FileOutputStream fos = new FileOutputStream(outputFile);
        try
        {
            printDeck(deck, fos);
            logger.log(Level.INFO, "PDF Generation complete.");
        } finally
        {
            fos.close();
        }
    }

    public void printDeck(DeckDescriptor deck, OutputStream os)
    {
        try
        {
            Rectangle pageSize = PageSize.A4;
            Document doc = new Document(pageSize);
            // TODO: output folder?
            PdfWriter writer = PdfWriter.getInstance(doc, os);
            doc.open();
            printDeck(doc, writer, deck);
            doc.close();
        } catch (DocumentException de)
        {
            logger.log(Level.WARNING, "Error printing deck", de);
        } catch (IOException ioe)
        {
            logger.log(Level.WARNING, "Error printing deck", ioe);
        }
    }

    private void printDeck(Document doc, PdfWriter writer, DeckDescriptor deck)
            throws IOException, DocumentException
    {
        // initialize for page
        float x = LEFT_MARGIN;
        float y = -1;
        drawCuttingLines(doc, writer);

        List<CardInDeck> section = deck.getCardsInSection("Main");
        // main deck
        if (section != null)
        {
            for (CardInDeck cid : section)
            {
                int amount = cid.getQuantity();
                if (!config.isPrintBasicLand() && cid.isBasicLand())
                {
                    amount = 0;
                }
                for (int i = 0; i < amount; i++)
                {
                    if (y < 0)
                    {
                        y = doc.getPageSize().getHeight() - HEIGHT - TOP_MARGIN;
                        drawCuttingLines(doc, writer);
                    }
                    CardInSet cis = setService.getCardInSet(cid);
                    Image image = getCachedImage(cis);
                    image.scaleToFit(WIDTH, HEIGHT);
                    image.setAbsolutePosition(x, y);
                    doc.add(image);
                    x += WIDTH;
                    if (x + WIDTH > doc.getPageSize().getWidth() - LEFT_MARGIN)
                    {
                        // row filled, start on the next row
                        x = LEFT_MARGIN;
                        y -= HEIGHT;
                        if (y < 0)
                        {
                            doc.newPage();
                        }
                    }
                }
            }
        }
        if (deck.getSideboard() != null)
        {
            // sideboard
            for (CardInDeck cid : deck.getSideboard())
            {
                int amount = cid.getQuantity();
                if (!config.isPrintBasicLand() && cid.isBasicLand())
                {
                    amount = 0;
                }
                for (int i = 0; i < amount; i++)
                {
                    if (y < 0)
                    {
                        y = doc.getPageSize().getHeight() - HEIGHT - TOP_MARGIN;
                        drawCuttingLines(doc, writer);
                    }
                    CardInSet cis = setService.getCardInSet(cid);
                    Image image = getCachedImage(cis);
                    image.scaleToFit(WIDTH, HEIGHT);
                    image.setAbsolutePosition(x, y);
                    doc.add(image);
                    x += WIDTH;
                    if (x + WIDTH > doc.getPageSize().getWidth() - LEFT_MARGIN)
                    {
                        // row filled, start on the next row
                        x = LEFT_MARGIN;
                        y -= HEIGHT;
                        if (y < 0)
                        {
                            doc.newPage();
                        }
                    }
                }
            }
        }
        // print deckList
        ColumnText ct = new ColumnText(writer.getDirectContent());
        ct.setSimpleColumn(x + 1, y + 1, x + (WIDTH / 2) - 2, y + HEIGHT - 2);
        ct.setLeading(9f);
        Font font = FontFactory.getFont(FontFactory.TIMES_ROMAN, 8f);
        ct.addText(new Phrase("Main Deck:", font));
        ct.addText(Chunk.NEWLINE);
        for (CardInDeck cid : deck.getMain())
        {
            ct.addText(new Phrase("" + cid.getQuantity() + " " + cid.getName(), font));
            ct.addText(Chunk.NEWLINE);
        }
        ct.go();

        ct = new ColumnText(writer.getDirectContent());
        ct.setSimpleColumn(x + (WIDTH / 2) + 1, y + 1, x + (WIDTH) - 2, y + HEIGHT - 2);
        ct.setLeading(9f);
        ct.addText(new Phrase("Sideboard:", font));
        ct.addText(Chunk.NEWLINE);
        if (deck.getSideboard() != null)
        {
            for (CardInDeck cid : deck.getSideboard())
            {
                ct.addText(new Phrase("" + cid.getQuantity() + " " + cid.getName(), font));
                ct.addText(Chunk.NEWLINE);
            }
        }
        ct.go();
    }

    private Image getCachedImage(CardInSet cis)
            throws BadElementException, IOException
    {
        Image result = imageCache.get(cis);
        if (result == null)
        {
            byte[] bytes = imageService.getImage(cis);
            try {
                result = Image.getInstance(bytes);
                imageCache.put(cis, result);
            } catch (IOException e)
            {
                logger.warning("error interpreting image: "+e.getMessage());
                e.printStackTrace();
                return null;
            }
        }
        return result;
    }

    private void drawCuttingLines(Document doc, PdfWriter writer)
    {
        PdfContentByte cb = writer.getDirectContent();
        cb.saveState();
        cb.setLineWidth(0);
        float position = LEFT_MARGIN;
        while (position < doc.getPageSize().getWidth() - LEFT_MARGIN + WIDTH)
        {
            cb.moveTo(position, 0);
            cb.lineTo(position, doc.getPageSize().getHeight());
            cb.stroke();
            position += WIDTH;
        }

        position = doc.getPageSize().getHeight() - TOP_MARGIN;
        while (position > 0)
        {
            cb.moveTo(0, position);
            cb.lineTo(doc.getPageSize().getWidth(), position);
            cb.stroke();
            position -= HEIGHT;
        }
        cb.restoreState();
    }
}
